﻿using System;
using System.Windows.Documents;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Controls;
using System.Collections.Generic;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows;
using System.IO;
using System.Diagnostics;
using System.Linq;

namespace NDependBuildInfoAddin
{
    public class NDependSummaryFactory : IBuildDetailFactory
    {
        private List<String> CriticalRules = new List<string>();
        private IBuildInformationNode ReportNode;
        private string ReportLinkUrl;

        /// <summary>
        /// This method should make all necessary server calls to aquire the data
        /// needed to display the sections and nodes for this factory.
        /// This method is called on a background thread (no UI work can be done).
        /// </summary>
        /// <param name="build">a reference to the IBuildDetail being displayed</param>
        public void LoadData(IBuildDetail build)
        {
            // beware this method will be called for each new build to display with the same instance of class
            // we need to reset all class members

            List<IBuildInformationNode> reportNodes = build.Information.GetNodesByType("NDependReportInfo", true);
            ReportNode = reportNodes.FirstOrDefault();

            CriticalRules.Clear();
            List<IBuildInformationNode> criticalRulesNodes = build.Information.GetNodesByType("NDependCriticalRuleInfo", true);
            foreach (IBuildInformationNode criticalRuleNode in criticalRulesNodes)
            {
                string criticalRule = getNodeField(criticalRuleNode, "Name");
                if (criticalRule != null) CriticalRules.Add(criticalRule);
            }

            List<IBuildInformationNode> reportLinkNodes = build.Information.GetNodesByType("NDependReportLink", true);
            ReportLinkUrl = getNodeField(reportLinkNodes.FirstOrDefault(), "Url");
        }

        private static string getNodeField(IBuildInformationNode node, string fieldName)
        {
            string result = null;
            if (node != null)
            {
                node.Fields.TryGetValue(fieldName, out result);
            }
            return result;
        }

        /// <summary>
        /// This method should return all IBuildDetailSections for this factory.
        /// </summary>
        /// <param name="view">a reference to IBuildDetailsView</param>
        /// <returns>array of IBuildDetailSections</returns>
        public IBuildDetailSection[] CreateSections(IBuildDetailView view)
        {
            return new IBuildDetailSection[] { new NDependSummarySection() };
        }

        /// <summary>
        /// This method should return all IBuildDetailNodes for this factory.
        /// </summary>
        /// <param name="view">a reference to IBuildDetailsView</param>
        /// <returns>array of IBuildDetailNodes</returns>
        public IBuildDetailNode[] CreateNodes(IBuildDetailView view)
        {
            Paragraph p = new Paragraph();

            if (ReportLinkUrl != null)
            {
                p.Inlines.Add(new Run("View the "));
                Hyperlink reportLink = new Hyperlink(new Run("NDepend HTML report"));
                reportLink.Tag = ReportLinkUrl;
                reportLink.Click += new RoutedEventHandler(this.hyperlink_Click);
                p.Inlines.Add(reportLink);
                p.Inlines.Add(new LineBreak());
                p.Inlines.Add(new LineBreak());
            }

            if (ReportNode != null)
            {
                // this is experimental code
 
                displayMetric(p, "Nb source files", "NbSourceFiles");
                displayMetric(p, "Nb lines of code", "NbLinesOfCode");
                displayMetric(p, "Nb lines of comment", "NbLinesOfComment");
                displayMetric(p, "Nb IL instructions", "NILInstruction");

                p.Inlines.Add(new LineBreak());

                int nbCqlQueries, nbCqlQueriesWarnings, nbCqlQueriesErrors, nbCriticalRules;
                // total number of CQL queries
                int.TryParse(getNodeField(ReportNode, "NbCqlQueries"), out nbCqlQueries);
                // Warnings include the critical rules
                int.TryParse(getNodeField(ReportNode, "NbCqlQueriesWarnings"), out nbCqlQueriesWarnings);
                // Errors are misunderstood rules, that ndepend could not compile or process (rare)
                int.TryParse(getNodeField(ReportNode, "NbCqlQueriesErrors"), out nbCqlQueriesErrors);
                // critical rules are warnings marked as "criticals"
                int.TryParse(getNodeField(ReportNode, "NbCriticalRules"), out nbCriticalRules);

                if (nbCqlQueries > 0)
                {
                    double percentWarnings = 100D * (nbCqlQueriesWarnings - nbCriticalRules) / nbCqlQueries;
                    double percentErrors = 100D * (nbCqlQueriesErrors) / nbCqlQueries;
                    double percentCriticals = 100D * (nbCriticalRules) / nbCqlQueries;
                    double percentSuccessfulRules = 100D - percentWarnings - percentErrors - percentCriticals;

                    p.Inlines.Add(new Run(String.Format("CQL Rules: Successful {0} + Warnings {1} (including {2} Criticals) + Errors {3} = Total {4}",
                        nbCqlQueries - nbCqlQueriesWarnings - nbCqlQueriesErrors,
                        nbCqlQueriesWarnings,
                        nbCriticalRules, 
                        nbCqlQueriesErrors,
                        nbCqlQueries)));

                    p.Inlines.Add(new LineBreak());
                    p.Inlines.Add(new Rectangle { Width = percentSuccessfulRules * 3, SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Green });
                    p.Inlines.Add(new Rectangle { Width = percentWarnings * 3, SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Yellow });
                    p.Inlines.Add(new Rectangle { Width = percentErrors * 3, SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Orange });
                    p.Inlines.Add(new Rectangle { Width = percentCriticals * 3, SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Red });

                    p.Inlines.Add(new LineBreak());
                    p.Inlines.Add(new LineBreak());

                }

                decimal percentageComment;
                Decimal.TryParse(getNodeField(ReportNode, "PercentageComment"), out percentageComment);

                if (percentageComment > 0)
                {
                    p.Inlines.Add(new Run("Comments coverage (percentage): "));
                    p.Inlines.Add(new LineBreak()); 
                    var commentedRect = new Rectangle { Width = Convert.ToDouble(percentageComment) * 3, SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Green };
                    var uncommentedRect = new Rectangle { Width = 300 - 3 * Convert.ToDouble(percentageComment), SnapsToDevicePixels = true, Height = 12, Stroke = Brushes.Black, StrokeThickness = 1, Fill = Brushes.Red };

                    p.Inlines.Add(commentedRect);
                    p.Inlines.Add(uncommentedRect);

                    p.Inlines.Add(new LineBreak());
                    p.Inlines.Add(new LineBreak());
                }
            }


            if (CriticalRules.Count > 0)
            {
                p.Inlines.Add(new Bold(new Run("Broken critial rules:")));
                foreach (String criticalRuleName in CriticalRules)
                {
                    p.Inlines.Add(new LineBreak());
                    p.Inlines.Add(new Run("\t" + criticalRuleName));
                }
            }

            return new IBuildDetailNode[] { 
                new NDependSummaryNode() { 
                    SectionName = "NDependSummary", 
                    Content = p
                }
            };
        }

        private void displayMetric(Paragraph p, string metricText, string name)
        {
            string metricValue = getNodeField(ReportNode, name);
            if (metricValue != null)
            {
                p.Inlines.Add(new Run(metricText + ": "));
                p.Inlines.Add(new Bold(new Run(metricValue)));
                p.Inlines.Add(new LineBreak());
            }
        }

        /// <summary>
        /// Returns the name of the factory (this name should never be localized).
        /// </summary>
        public string Name
        {
            get { return "NDependSummaryFactory"; }
        }

        private void hyperlink_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Hyperlink)
            {
                String uri = ((Hyperlink)sender).Tag as String;
                Process.Start("explorer", uri);
            }
        }
    }



    public class NDependSummarySection : IBuildDetailSection
    {
        /// <summary>
        /// Returns the header for this section. Usually this is just a localized string.
        /// However, it can also return a Windows Presentation Foundation document paragraph 
        /// that contains any valid elements for display.
        /// </summary>
        public object Header
        {
            get { return "NDepend Summary"; }
        }

        /// <summary>
        /// Returns the name of the section (this name should never be localized).
        /// </summary>
        public string Name
        {
            get { return "NDependSummary"; }
        }

        /// <summary>
        /// Returns the relative priority of this section. This value is used to sort 
        /// the sections in ascending order before display. Standard sections have 
        /// values like 100, 200, 300, etc.
        /// </summary>
        public int Priority
        {
            get { return 150; }
        }
    }

    public class NDependSummaryNode : IBuildDetailNode
    {
        public object Content { get; set; }
        public string SectionName { get; set; }
    }
}